Key-Value Observing,是iOS中的一种设计模式,用于检测对象的某些属性的实时变化情况并作出响应,这能够为我们在开发强交互、响应式应用以及实现视图和模型的双向绑定时提供大量的帮助。
KVO主要用于视图交互方面,比如界面的某些数据变化了,界面的显示也跟着需要变化,那就要建立数据和界面的关联。
原生KVO
订阅 :
observer
:KVO通知的对象注册。观察者必须实现键值观察方法keyPath
:关联路径,即被观察的属性。不能设置为niloptions
:options可选值是一个NSKeyValueObservingOptions枚举值;首先了解一个概念,即KVO响应方法有一个NSDictionary类型参数change,这个字典中会有一个与被监听属性相关的值,譬如被改变之前的值、新值等,NSDictionary中有什么值由订阅时的options值决定,options可取值如下:- NSKeyValueObservingOptionNew: 指示change字典中包含新属性值;
- NSKeyValueObservingOptionOld: 指示change字典中包含旧属性值;
- NSKeyValueObservingOptionInitial: 相对复杂一些,NSKeyValueObserving.h文件中有详细说明,此处略过;
- NSKeyValueObservingOptionPrior: 相对复杂一些,NSKeyValueObserving.h文件中有详细说明,此处略过;
context
:需要传递给观察者的上下文信息
响应 :
keyPath
:keyPath的类型是NSString,这导致了我们使用了错误的keyPath而不自知,譬如将@"contentSize"
错误写成@"contentsize"
,更好的方法是使用NSStringFromSelector(SEL aSelector)
方法,即改为NSStringFromSelector(@selector(contentSize))
.object
:被观察者的对象change
:根据上面的Options设置,给出对应的属性值context
:使用 context 上下文以及其它辅助手段才能够帮助我们更加精准地确定被观测的对象。假如父类(ClassA)和子类(ClassB)都监听了同一个对象怎么办?是ClassB处理呢还是交给父类ClassA的observeValueForKeyPath:ofObject:change:context:
处理呢?更复杂一点,如果子类的子类(设为ClassC)也监听了同一个对象,当ClassB接收到ClassC的[super observeValueForKeyPath:keyPath ofObject:object change:change context:context]
消息时又该如何处理呢?
比较靠谱的做法是自己的屁股自己擦。ClassB的observe事务在ClassB中处理,怎么知道是自己的事务还是ClassC传上来的事务呢?用context参数判断!在add observer时为context参数设置一个独一无二的值即可,在responding处理时对这个context值进行检验。如此就解决了问题,但这需要靠用户(各个层级类的程序员用户)自觉遵守。
取消订阅 :
注:使用KVO消息传递机制需要注意两点,①观察者必须知道被观察对象,即在同一作用域;②观察者还需要知道被观察对象的生命周期,因为在销毁发送者对象之前,需要取消观察者的注册。
由于系统提供的API使用起来非常不优雅,使用过程也是非常麻烦需要注意很多问题:
- 需要手动移除观察者,且移除观察者的时机必须合适;
- 注册观察者的代码和事件发生处的代码上下文不同,传递上下文是通过
void *
指针; - 需要覆写
-observeValueForKeyPath:ofObject:change:context:
方法,比较麻烦; - 在复杂的业务逻辑中,准确判断被观察者相对比较麻烦,有多个被观测的对象和属性时,需要在方法中写大量的 if 进行判断;
如何优雅的解决呢? - 使用 Facebook 开源的 KVOController,如下
KVOController
KVOController是Facebook开源的框架使用在iOS,maxOS上,是对Cocoa中KVO的封装。 提供了block
和@selector(SEL)
的回调操作,使用起来既简洁优雅又保证线程安全。相比原生KVO优点如下:
- 不需要手动移除观察者;
- 实现 KVO 与事件发生处的代码上下文相同,不需要跨方法传参数;
- 使用 block 来替代方法能够减少使用的复杂度,提升使用 KVO 的体验;
- 每一个
keyPath
会对应一个属性,不需要在block
中使用if
判断keyPath
(就像UIButton处理事件一样方便);
使用
|
以上就是KVO的简单介绍和KVOController的使用。本文参考自如何优雅地使用 KVO,更多详细深入的内容请参考:☟